/*
 * Copyright (c) 2011 Sapienza University of Rome.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the
 *   distribution.
 * - Neither the name of the Sapienza University of Rome nor the names of
 *   its contributors may be used to endorse or promote products derived
 *   from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL SAPIENZA
 * UNIVERSITY OF ROME OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/**
 * This component enables DISSense to be used with the bundled java application "DISSense Manager".
 * This application allows a user to retrieve schedule information from the Sink, to keep the scehdule
 * of DISSense synchronized with the application, and, to update the schedule by changing the time when the
 * active phase of DISSense is scheduled as much as by changing the sampling period (min: 1 minute, max: 1 hour).
 *
 * @author Ugo Colesanti <colesanti@dis.uniroma1.it>
 * @version 1.01 (October 10, 2011)
 */

#include "NtpManager.h"
module NtpManagerC{
  provides{
	  interface NtpSynchronization ;
  }
  uses{
	  interface Receive ;// as ReceiveNtpSyncMsg ;
	  interface AMSend ; //as SendNtpSyncMsg ;
	  interface Receive as HTReceive ;
	  interface AMSend as HTSend ;
	  interface ScheduleManagement ;
	  interface Reset ;
	  interface Timer<TMilli> as Timer ;
	  interface Leds ;

  }
}
implementation{
	task void sendUpdateMsg() ;
	task void sendHeartBeat() ;
	task void sendNtpMsg() ;

	bool uartbusy ;
	message_t uartbuf;

	uint32_t drift = 0 ;
	uint32_t schedule_drift = 0 ;

	bool connected = FALSE ;
	bool resynchronizing = FALSE ;
	uint8_t lostConnection = 0 ;
	bool waitHeartBeat = FALSE ;

	enum{
		HEART_BEAT_WATCHDOG = 6144, // waits the heart beat for 6 seconds
		CONNECTION_LOST_THRESHOLD = 6, // number of connection lost before setting to disconnected
		SINK_FLAG = 0x1,
		RESYNCHRONIZING_FLAG_OFFSET = 3,
	};

	event message_t*
		HTReceive.receive(message_t* msg, void *payload, uint8_t len) {
			heartBeatMsg_t* rx_msg = (heartBeatMsg_t*) payload ;
			//call Leds.led0On() ;
			if(sizeof(heartBeatMsg_t) == len){
				if(waitHeartBeat == FALSE){ // starts ht
					if(rx_msg->type == HEART_BEAT_REQ){
						call Timer.startOneShot(HEART_BEAT_WATCHDOG) ; // waits 6 seconds (it should take less)
						waitHeartBeat = TRUE ;
						post sendHeartBeat() ;

					}
				}
				else{ // ok, I'm connected and everything goes fine
					if(rx_msg->type == HEART_BEAT_REQ){
						connected = TRUE ;
						lostConnection = 0 ;
						call Leds.led0On() ;
						call Leds.led2Off() ;
						call Timer.startOneShot(HEART_BEAT_WATCHDOG) ; // waits 6 seconds (it should take less)
						waitHeartBeat = TRUE ;
						post sendHeartBeat() ;
					}
				}
			}

		return msg ;

	}

	event message_t*
	Receive.receive(message_t* msg, void *payload, uint8_t len) {
		sinkGatewayMsg_t* rx_msg = (sinkGatewayMsg_t*) payload ;
		if(sizeof(sinkGatewayMsg_t) == len){
			switch(rx_msg->type){
			case STATE_REQUEST:
				post sendUpdateMsg() ;
				break;
			case CHANGE_STATE_TO_SINK:
				break;
			case NTP_ADJUST_REQ:
				drift = rx_msg->current_timestamp ;
				post sendNtpMsg() ;
				break;
			case SCHEDULE_UPDATE_REQ:
				resynchronizing = TRUE ;
				call Leds.led1Toggle() ;
				schedule_drift = rx_msg->current_timestamp ;
				call ScheduleManagement.setNextSamplingPeriod(rx_msg->next_sampling_period) ;
				break ;
			case RESET_NODE:
				call Reset.reset(rx_msg->next_sampling_period) ;
				break;
			}
		}

		return msg ;

	}

	task void sendHeartBeat(){
		heartBeatMsg_t* msg ;
		//		call Leds.led1On() ;
		if (uartbusy == FALSE) {
			msg = call HTSend.getPayload(&uartbuf, sizeof(heartBeatMsg_t)) ;
			if(msg == NULL){

			}
			else{
				msg->type = HEART_BEAT_ACK ;
				if(call HTSend.send(AM_BROADCAST_ADDR, &uartbuf, sizeof(sinkGatewayMsg_t)) != SUCCESS){
			  }
			  else{
				  uartbusy = TRUE ;
			  }
			}
		}

	}

	task void sendUpdateMsg(){
		sinkGatewayMsg_t* msg ;
		if (uartbusy == FALSE) {
			msg = call AMSend.getPayload(&uartbuf, sizeof(sinkGatewayMsg_t)) ;
			if(msg == NULL){

			}
			else{
				msg->type = STATE_REPLY ;
				msg->state = SINK_FLAG | (resynchronizing << RESYNCHRONIZING_FLAG_OFFSET); // is sink -> to change in future!

				msg->current_sampling_period = call ScheduleManagement.getCurrentSamplingPeriod() ;
				msg->current_schedule_gt = call ScheduleManagement.getCurrentGt() ;
				msg->current_schedule_ri = call ScheduleManagement.getCurrentRi() ;
				msg->current_schedule_dci = call ScheduleManagement.getCurrentDci() ;
				msg->current_schedule_skip = call ScheduleManagement.getCurrentSkip() ;
				msg->current_timestamp = call Timer.getNow() ;
				msg->next_sampling_period = call ScheduleManagement.getNextSamplingPeriod() ;
				msg->time_to_next_sampling_period = call ScheduleManagement.getTimeToNextSP() ;
				msg->next_sampling_period_fired = call ScheduleManagement.getNextSamplingPeriodFired() ;

			  if(call AMSend.send(AM_BROADCAST_ADDR, &uartbuf, sizeof(sinkGatewayMsg_t)) != SUCCESS){

			  }
			  else{
				  uartbusy = TRUE ;
			  }
			}
		}


	}

	task void sendNtpMsg(){
			sinkGatewayMsg_t* msg ;
			if (uartbusy == FALSE) {
				msg = call AMSend.getPayload(&uartbuf, sizeof(sinkGatewayMsg_t)) ;
				if(msg == NULL){

				}
				else{
					msg->type = NTP_ADJUST_ACK ;
					msg->state = SINK_FLAG ; // is sink -> to change in future!

					msg->current_sampling_period = call ScheduleManagement.getCurrentSamplingPeriod() ;
					msg->current_schedule_gt = call ScheduleManagement.getCurrentGt() ;
					msg->current_schedule_ri = call ScheduleManagement.getCurrentRi() ;
					msg->current_schedule_dci = call ScheduleManagement.getCurrentDci() ;
					msg->current_schedule_skip = call ScheduleManagement.getCurrentSkip() ;
					msg->current_timestamp = call Timer.getNow() ;
					msg->next_sampling_period = call ScheduleManagement.getNextSamplingPeriod() ;
					msg->time_to_next_sampling_period = call ScheduleManagement.getTimeToNextSP() ;
					msg->next_sampling_period_fired = call ScheduleManagement.getNextSamplingPeriodFired() ;



				  if(call AMSend.send(AM_BROADCAST_ADDR, &uartbuf, sizeof(sinkGatewayMsg_t)) != SUCCESS){
				  }
				  else{
					  uartbusy = TRUE ;
				  }
				}
			}


		}

	event void Timer.fired(){
		if(waitHeartBeat == TRUE){
			lostConnection++ ;
			if(lostConnection >= CONNECTION_LOST_THRESHOLD){
				waitHeartBeat = FALSE ;
				lostConnection = 0 ;
				connected = FALSE ;
				call Leds.led2On() ;
				call Leds.led0Off() ;
//				printf("Connection lost.\n") ;
//				printfflush() ;
			}
			else{
				call Timer.startOneShot(HEART_BEAT_WATCHDOG) ;
			}
		}
	}


	event void AMSend.sendDone(message_t *msg, error_t error) {
		  uartbusy = FALSE;
	}

	event void HTSend.sendDone(message_t *msg, error_t error){
		uartbusy = FALSE ;
	}

	command uint32_t NtpSynchronization.getNtpDrift(){
		 return drift ;
	}

	command uint32_t NtpSynchronization.getScheduleDrift(){
		return schedule_drift ;
	}

	command void NtpSynchronization.resetFields(){
		drift = 0 ;
		schedule_drift = 0 ;
		if(resynchronizing){
			resynchronizing = FALSE ;
			post sendUpdateMsg() ;
		}
	}

}
